package org.net5ijy.commons.http;

import static org.net5ijy.commons.http.constants.CharsetNames.CHARSET_UTF8;
import static org.net5ijy.commons.http.constants.HttpHeaderNames.HEADER_NAME_CONTENT_TYPE;
import static org.net5ijy.commons.http.constants.HttpHeaderNames.HEADER_NAME_RANGE;
import static org.net5ijy.commons.http.constants.Medias.*;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.net.ssl.SSLContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpMessage;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.net5ijy.commons.http.response.FileResponseHolder;
import org.net5ijy.commons.http.response.HtmlResponseHolder;
import org.net5ijy.commons.web.util.XmlUtil;

/**
 * 使用HttpClient实现的Http
 *
 * @author xuguofeng
 * @date 2020/7/14 9:23
 */
@Slf4j
public class HttpClientHttp extends AbstractHttp {

  private static final int BUFFER_SIZE = 1024 * 1024 * 20;

  private static final int CONNECTION_TIMEOUT = 20_000;

  private static final int SOCKET_TIMEOUT = 20_000;

  private int bufferSize = BUFFER_SIZE;

  private int connectionTimeout = CONNECTION_TIMEOUT;

  private int socketTimeout = SOCKET_TIMEOUT;

  private CloseableHttpClient httpclient;

  public HttpClientHttp() {
    this.createHttpClient();
  }

  public HttpClientHttp(Proxy proxy, String proxyName, String proxyPassword) {
    super(proxy, proxyName, proxyPassword);
    this.createHttpClient();
  }

  public HttpClientHttp(String proxyHost, int proxyPort, String proxyName,
      String proxyPassword) {
    super(proxyHost, proxyPort, proxyName, proxyPassword);
    this.createHttpClient();
  }

  private void createHttpClient() {

    try {

      // ssl信任所有
      SSLContext ctx = new SSLContextBuilder()
          .loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true)
          .build();

      // 设置协议http和https对应的处理socket链接工厂的对象
      SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(ctx);

      // 不设置代理
      if (this.proxy == null) {
        this.httpclient = HttpClients.custom()
            .setDefaultRequestConfig(
                RequestConfig.custom().setRedirectsEnabled(false).build()
            )
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .build();
      } else {

        InetSocketAddress address = (InetSocketAddress) this.proxy.address();

        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
            new AuthScope(address.getHostName(), address.getPort()),
            new UsernamePasswordCredentials(proxyName, proxyPassword));

        this.httpclient = HttpClients.custom()
            .setDefaultRequestConfig(
                RequestConfig.custom().setRedirectsEnabled(false).build()
            )
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .setProxy(new HttpHost(address.getHostName(), address.getPort()))
            .setDefaultCredentialsProvider(credentialsProvider)
            .build();
      }

    } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
      throw new RuntimeException(e);
    }
  }

  public int getBufferSize() {
    return bufferSize;
  }

  public void setBufferSize(int bufferSize) {
    this.bufferSize = bufferSize;
  }

  public int getConnectionTimeout() {
    return connectionTimeout;
  }

  public void setConnectionTimeout(int connectionTimeout) {
    this.connectionTimeout = connectionTimeout;
  }

  public int getSocketTimeout() {
    return socketTimeout;
  }

  public void setSocketTimeout(int socketTimeout) {
    this.socketTimeout = socketTimeout;
  }

  @Override
  public String doGet(String url) {
    return this.doGet(url, null);
  }

  @Override
  public String doGetWithHeaders(String url, Map<String, String> headers) {
    return this.doGet(url, null, headers);
  }

  @Override
  public String doGet(String url, Map<String, String> params) {
    return this.doGet(url, params, null);
  }

  @Override
  public String doGet(String url, Map<String, String> params, Map<String, String> headers) {

    try {

      // 拼接请求参数
      String paramStr = getRequestParameterString(url, params);

      // 创建HttpGet
      HttpGet httpget = new HttpGet(url + paramStr);

      // 设置请求头
      addHeaders(httpget, headers);

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpget)) {

        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
          // 响应内容
          return EntityUtils.toString(entity, CHARSET_UTF8);
        }
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public String doPostJson(String url, String json) {
    return doPostJson(url, json, null);
  }

  @Override
  public String doPostJson(String url, String json, Map<String, String> headers) {

    try {
      // 创建HttpPost
      HttpPost httpPost = new HttpPost(url);
      httpPost.addHeader(HEADER_NAME_CONTENT_TYPE, APPLICATION_JSON);
      httpPost.setEntity(new StringEntity(json, CHARSET_UTF8));

      // 设置请求头
      addHeaders(httpPost, headers);

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpPost)) {

        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
          return EntityUtils.toString(entity, CHARSET_UTF8);
        }
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public String doPostJson(String url, Object jsonAble) {
    return this.doPostJson(url, jsonAble, null);
  }

  @Override
  public String doPostJson(String url, Object jsonAble, Map<String, String> headers) {
    try {
      // 把参数转为JSON字符串
      ObjectMapper mapper = new ObjectMapper();
      String body = mapper.writeValueAsString(jsonAble);
      return this.doPostJson(url, body, headers);
    } catch (JsonProcessingException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public String doPostXml(String url, String xml) {
    return this.doPostXml(url, xml, null);
  }

  @Override
  public String doPostXml(String url, String xml, Map<String, String> headers) {
    try {

      // 创建HttpPost
      HttpPost httpPost = new HttpPost(url);
      httpPost.addHeader(HEADER_NAME_CONTENT_TYPE, TEXT_XML_UTF8);
      httpPost.setEntity(new StringEntity(xml, CHARSET_UTF8));

      // 设置请求头
      addHeaders(httpPost, headers);

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpPost)) {

        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
          // 响应内容
          return EntityUtils.toString(entity, CHARSET_UTF8);
        }
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public String doPostXml(String url, Map<String, String> xml) {
    return doPostXml(url, XmlUtil.map2Xml(xml, "xml"));
  }

  @Override
  public String doPostXml(String url, Map<String, String> xml, Map<String, String> headers) {
    return doPostXml(url, XmlUtil.map2Xml(xml, "xml"), headers);
  }

  @Override
  public String doPost(String url) {
    return doPostForm(url, null);
  }

  @Override
  public String doPost(String url, Map<String, String> headers) {
    return doPostForm(url, null);
  }

  @Override
  public String doPostForm(String url, Map<String, String> params) {
    return this.doPostForm(url, params, null);
  }

  @Override
  public String doPostForm(String url, Map<String, String> params, Map<String, String> headers) {

    try {
      // 创建HttpPost
      HttpPost httpPost = new HttpPost(url);

      // 设置请求头
      addHeaders(httpPost, headers);

      // 请求参数
      if (params != null && !params.isEmpty()) {
        List<NameValuePair> paramsList = new ArrayList<>();

        for (Entry<String, String> e : params.entrySet()) {
          paramsList.add(new BasicNameValuePair(e.getKey(), e.getValue()));
        }

        HttpEntity entity = new UrlEncodedFormEntity(paramsList, CHARSET_UTF8);
        // 设置参数
        httpPost.setEntity(entity);
      }

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
          return EntityUtils.toString(entity, CHARSET_UTF8);
        }
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public String doPostFormWithFile(String url, Map<String, String> params, File file, String name) {
    return this.doPostFormWithFile(url, params, file, name, null);
  }

  @Override
  public String doPostFormWithFile(
      String url, Map<String, String> params, File file, String name, Map<String, String> headers) {

    // 超时设置
    RequestConfig requestConfig = RequestConfig
        .custom()
        .setConnectTimeout(this.connectionTimeout)
        .setSocketTimeout(this.socketTimeout)
        .build();

    try {

      // 创建HttpPost
      HttpPost httpPost = new HttpPost(url);

      // 超时设置
      httpPost.setConfig(requestConfig);

      // 设置请求头
      addHeaders(httpPost, headers);

      ContentType contentType = ContentType.create(TEXT_PLAIN, Consts.UTF_8);

      // multipart请求体构建器
      MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();

      // 添加上传文件
      multipartEntityBuilder.addBinaryBody(name, file);

      // 表单数据
      for (Entry<String, String> e : params.entrySet()) {
        // String val = URLEncoder.encode(e.getValue(), "UTF-8");
        String val = e.getValue();
        // multipartEntityBuilder.addTextBody(e.getKey(), val);
        multipartEntityBuilder.addPart(e.getKey(), new StringBody(val, contentType));
      }

      // 创建HttpEntity
      httpPost.setEntity(multipartEntityBuilder.build());

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
          // 响应内容
          return EntityUtils.toString(entity, CHARSET_UTF8);
        }
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public String upload(String url, String path, String name) {
    return upload(url, new File(path), name);
  }

  @Override
  public String upload(String url, String path, String name, Map<String, String> headers) {
    return upload(url, new File(path), name, headers);
  }

  @Override
  public String upload(String url, File file, String name) {
    return this.upload(url, file, name, null);
  }

  @Override
  public String upload(String url, File file, String name, Map<String, String> headers) {

    // 超时设置
    RequestConfig requestConfig = RequestConfig
        .custom()
        .setConnectTimeout(this.connectionTimeout)
        .setSocketTimeout(this.socketTimeout)
        .build();

    try {

      // 创建HttpPost
      HttpPost httpPost = new HttpPost(url);
      // 超时设置
      httpPost.setConfig(requestConfig);

      // 设置请求头
      addHeaders(httpPost, headers);

      // multipart请求体构建器
      MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
      // 添加上传文件
      multipartEntityBuilder.addBinaryBody(name, file);

      // 创建HttpEntity
      httpPost.setEntity(multipartEntityBuilder.build());

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
          // 响应内容
          return EntityUtils.toString(entity, CHARSET_UTF8);
        }
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public FileResponseHolder download(
      String url, String filePath, Map<String, String> headers, Map<String, String> params) {
    return download(url, new File(filePath), headers, params);
  }

  @Override
  public FileResponseHolder download(
      String url, String filePath, Map<String, String> headers, Map<String, String> params,
      boolean append) {

    File file = new File(filePath);

    if (file.exists()) {
      if (append) {
        long length = file.length();
        String range = "bytes=" + length + "-";
        headers.put(HEADER_NAME_RANGE, range);
        return download(url, file, headers, params);
      } else {
        file.deleteOnExit();
        return download(url, file, headers, params);
      }
    } else {
      return download(url, file, headers, params);
    }
  }

  private FileResponseHolder download(
      String url, File file, Map<String, String> headers, Map<String, String> params) {

    FileResponseHolder responseHolder = new FileResponseHolder();

    try {

      // 拼接请求参数
      String paramStr = getRequestParameterString(url, params);

      // 创建HttpGet
      HttpGet httpget = new HttpGet(url + paramStr);

      // 设置请求头
      addHeaders(httpget, headers);

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpget)) {

        // 获取响应状态码
        responseHolder.setStatusCode(response.getStatusLine().getStatusCode());

        // 获取响应头
        for (Header header : response.getAllHeaders()) {
          responseHolder.addHeader(header.getName(), header.getValue());
        }

        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {

          // 获取响应content-type
          if (entity.getContentType() != null) {
            responseHolder.setContentType(entity.getContentType().getValue());
          }

          responseHolder.setFile(file);

          long total, contentLength = getContentLength(responseHolder), current = 0;

          if (file.exists()) {
            if (Long.valueOf(contentLength).equals(file.length())) {
              return responseHolder;
            }
            total = file.length() + contentLength;
            current = file.length();
          } else {
            total = contentLength;
          }

          // 响应内容
          try (InputStream in = entity.getContent();
              OutputStream out = new FileOutputStream(file, true)) {

            byte[] buf = new byte[bufferSize];
            int len = in.read(buf);
            while (len > -1) {
              out.write(buf, 0, len);
              current += len;
              printProgress(total, current, false);
              len = in.read(buf);
            }
            printProgress(total, current, true);
          }
        }
      }
    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return responseHolder;
  }

  @Override
  public HtmlResponseHolder html(
      String url, Map<String, String> headers, Map<String, String> params) {

    try {

      // 拼接请求参数
      String paramStr = getRequestParameterString(url, params);

      // 创建HttpGet
      HttpGet httpget = new HttpGet(url + paramStr);

      // 设置请求头
      addHeaders(httpget, headers);

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpget)) {
        return response2Holder(response);
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public HtmlResponseHolder postHtml(
      String url, Map<String, String> headers, Map<String, String> params) {

    try {

      // 创建httpPost
      HttpPost httpPost = new HttpPost(url);

      // 请求参数
      if (params != null && !params.isEmpty()) {

        List<NameValuePair> paramsList = new ArrayList<>();

        for (Entry<String, String> e : params.entrySet()) {
          paramsList.add(new BasicNameValuePair(e.getKey(), e.getValue()));
        }

        HttpEntity entity = new UrlEncodedFormEntity(paramsList, CHARSET_UTF8);
        // 设置参数
        httpPost.setEntity(entity);
      }

      // 设置请求头
      addHeaders(httpPost, headers);

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
        return response2Holder(response);
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }

  @Override
  public String delete(String url, Map<String, String> headers) {

    try {
      // 创建HttpDelete
      HttpDelete httpDelete = new HttpDelete(url);
      httpDelete.addHeader(HEADER_NAME_CONTENT_TYPE, APPLICATION_JSON);

      // 设置请求头
      addHeaders(httpDelete, headers);

      // 发送请求并获取响应
      try (CloseableHttpResponse response = httpclient.execute(httpDelete)) {

        // 获取响应实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
          return EntityUtils.toString(entity, CHARSET_UTF8);
        }
      }

    } catch (IOException e) {
      log.error(e.getMessage(), e);
    }

    return null;
  }

  @Override
  public void close() throws IOException {
    if (this.httpclient != null) {
      this.httpclient.close();
    }
  }

  private HtmlResponseHolder response2Holder(CloseableHttpResponse response) throws IOException {

    // 封装响应
    HtmlResponseHolder responseHolder = new HtmlResponseHolder();

    // 获取响应状态码
    responseHolder.setStatusCode(response.getStatusLine().getStatusCode());

    // 获取响应头
    for (Header header : response.getAllHeaders()) {
      responseHolder.addHeader(header.getName(), header.getValue());
    }

    // 获取响应实体
    HttpEntity entity = response.getEntity();

    if (entity != null) {
      // 获取响应content-type
      Header contentType = entity.getContentType();
      if (contentType != null) {
        responseHolder.setContentType(entity.getContentType().getValue());
      }
      // 响应内容
      responseHolder.setContent(EntityUtils.toString(entity, CHARSET_UTF8));
    }

    return responseHolder;
  }

  private void addHeaders(HttpMessage httpMessage, Map<String, String> headers) {
    if (headers != null && headers.size() > 0) {
      for (Entry<String, String> entry : headers.entrySet()) {
        httpMessage.setHeader(entry.getKey(), entry.getValue());
      }
    }
  }
}
